﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#ifndef _UXS_GL_BATCH_RENDER_H
#define _UXS_GL_BATCH_RENDER_H

#pragma once

#include "../shell/GLStateManager.h"
#include "../shell/GLMesh.h"
#include "GLPrimitives.h"

namespace UX_STL
{
namespace GLShell
{


	//
	// BatchRender
	//

	class UX_EXPORT GLBatchRender : public GLBaseObject
	{
		friend class GLSystem;

	public:
		class IVertexBuffer
		{
		public:
			virtual void	Reserve(usize uSize) = 0;
			virtual void	Destroy() = 0;
			virtual GLuint	GetBufferId() const = 0;
			virtual void	SetData(const void *pData, usize uSize) = 0;
		};

		class UX_EXPORT GLBatch : public GLSubMesh
		{
			friend class GLBatchRender;

		private:
			uint8	_uVertexSize;

		public:
			GLBatch(): _uVertexSize(1) {}
			GLBatch(GLBatch &cBatch): GLSubMesh( cBatch ), _uVertexSize( cBatch._uVertexSize ) {}
		};

		typedef void (*PDrawBatchProc_t)(void *pParameter, const GLBatch &cBatch);


	private:
		typedef array< GLBatch >	batch_array_t;
		

		class GLVertexBuffer : protected GLBuffer, public IVertexBuffer
		{
		public:
			virtual void	Reserve(usize uSize)					{ Create(gl_buffer::ARRAY);  Allocate( uSize, gl_usage::DYNAMIC_DRAW ); }
			virtual void	Destroy()								{ GLBuffer::Destroy(); }
			virtual GLuint	GetBufferId() const						{ return Id(); }
			virtual void	SetData(const void *pData, usize uSize)	{ GLBuffer::SetData( pData, uSize, gl_usage::DYNAMIC_DRAW ); }
		};

		struct TBatch
		{
			void	*			pParameter;
			PDrawBatchProc_t	pDrawProc;
			gl_primitive::type	eMode;
			usize				uOffset,		// offset in vertex array
								uVerSize,		// vertices data size
								uBatchType;
			float				fDepth;			// for Z-sorting
			///
			TBatch():
				eMode(gl_primitive::_UNKNOWN), pParameter(null), pDrawProc(null),
				uOffset(0), uBatchType(0), uVerSize(0), fDepth(0.f) {}
			
			TBatch(gl_primitive::type mode, void *param, PDrawBatchProc_t proc, usize offset, usize type, float depth):
				eMode(mode), pParameter(param), pDrawProc(proc), uOffset(offset),
				uVerSize(0), uBatchType(type), fDepth(depth) {}
		};

	private:
		batch_array_t				_aBatchTypes;
		array< TBatch >				_aBatches;
		binary_array_t				_aVertices;
		static_stack< frect, 16 >	_sScissorStack;

		Ptr< IVertexBuffer >		_pVB;
		GLVertexBuffer				_cVB;
		
		usize						_uDefault2DBatch,
									_uDefault2DGSBatch,
									_uDefault3DBatch;
		float						_fCurrentZ;
		float						_fZStep;
		float						_fDefZ;
		bool						_bChanged	: 1;
		bool						_bScissor	: 1;

	protected:
		void _UpdateBuffers();
		void _ZSort(usize uFirst, usize uCount);
		void _Destroy();

	public:
		GLBatchRender();
		~GLBatchRender();

		bool  Create(IVertexBuffer *pVB = null);
		void  SetDefaultBatches(usize uDefault2DBatch, usize uDefault2DGSBatch, usize uDefault3DBatch);

		bool  AddBatchType(const GLVerticesAttribs_t &aAttribs, uint8 uVertSize, usize &uIndex);
		
		void  NewBatch(void *pParam, PDrawBatchProc_t pDraw, gl_primitive::type eMode,
						usize uBatchTypeIndex = 0, float fDepth = 0.f);					// create new batch
		void  NewBatchC(void *pParam, PDrawBatchProc_t pDraw, gl_primitive::type eMode,
						usize uBatchTypeIndex = 0, float fDepth = 0.f);					// compare with prev
		void  AddVertices(const void *pVerts, usize uSize);

		bool  RemoveBatch(usize uNumb);
		bool  RemoveBatch(void *pParam, PDrawBatchProc_t pDraw, gl_primitive::type eMode, usize uBatchTypeIndex);

		void  Clear();
		bool  IsValid()	const;

		void  NewElement()								{ _fCurrentZ += _fZStep; }

		bool  Flush();
		bool  FlushRange(usize uFirst, usize uCount);

		void  SortZ()									{ _ZSort( 0, _aBatches.Count() ); }
		void  SortZRange(usize uFirst, usize uCount)	{ _ZSort( uFirst, uCount ); }

		float GetCurrentZ()		const					{ return _fCurrentZ; }
		usize GetBatchNumber()	const					{ return _aBatches.Count()-1; }
		usize GetVBOSize()		const					{ return _aVertices.Size(); }
		bool  Empty()			const					{ return _aVertices.Empty(); }

		void  SetZOffsetStep(float fStep)				{ _fZStep = fStep; }
		void  SetDefaultZ(float fDefZ)					{ _fDefZ  = fDefZ; }

		usize Default2DBatch()		const				{ return _uDefault2DBatch; }
		usize Default2DGSBatch()	const				{ return _uDefault2DGSBatch; }
		usize Default3DBatch()		const				{ return _uDefault3DBatch; }
		


		//-------- Scissor Stack --------//
		
		bool ScissorState() const						{ return _bScissor; }
		void EnableScissor(bool bEnabled)				{ _bScissor = bEnabled; }
		void SetScissor(const frect &sRect)				{ _sScissorStack.Set( sRect ); }
		void PushScissor(const frect &sRect)			{ _sScissorStack.Push( sRect ); }
		void PushScissor()								{ _sScissorStack.Push(); }
		void PopScissor()								{ _sScissorStack.Pop(); }

		const frect & GetScissor() const				{ return _sScissorStack.Get(); }

		bool ScissorTest(const fvec2 &vPoint) const		{ return _sScissorStack.Get().IsInnerPoint( vPoint ); }
		bool ScissorTest(const frect &sRect) const		{ return _sScissorStack.Get().IntersectRect( sRect ); }
		


		//---------- 3D Render ----------//
		
		void Vertex3d(const TVertex3D &sVertex);
		void Polygons3d(const TVertex3D *pPolygons, uint uCount);
		
		// GL_POINT //
		void Point3d(const TVertex3D &sPoint);
		void Point3d(void *pParam, PDrawBatchProc_t pDraw, const TVertex3D &sPoint);
		
		// GL_LINE //
		void Line3d(const TLine3D &sLine);
		void Lines3d(const TLine3D *pLines, uint uCount);
		void Line3d(void *pParam, PDrawBatchProc_t pDraw, const TLine3D &sLine);
		void Lines3d(void *pParam, PDrawBatchProc_t pDraw, const TLine3D *pLines, uint uCount);
		
		// GL_LINE_STRIP //
		void Lines3dS(const TVertex3D *pPoints, uint uCount);
		void Lines3dS(void *pParam, PDrawBatchProc_t pDraw, const TVertex3D *pPoints, uint uCount);

		// GL_TRIANGLES //
		void Triangle3d(const TTriangle3D &sTriangle);
		void Triangle3d(void *pParam, PDrawBatchProc_t pDraw, const TTriangle3D &sTriangle);

		// GL_TRIANGLE_STRIP //
		void Rect3dS(const TRect3D &sRect);
		void Rect3dS(void *pParam, PDrawBatchProc_t pDraw, const TRect3D &sRect);
		


		//---------- 2D Render ----------//
		// for all quads:	+y
		//					_|_+x
		//					 |

		void Vertex2d(const TVertex2D &sVertex);
		void Polygons2d(const TVertex2D *pPolygons, uint uCount);

		// GL_POINT //
		void Point2d(const TVertex2D &sPoint);
		void Point2d(void *pParam, PDrawBatchProc_t pDraw, const TVertex2D &sPoint);

		// GL_LINE //
		void Line2d(const TLine2D &sLine);
		void Lines2d(const TLine2D *pLines, uint uCount);
		void Line2d(void *pParam, PDrawBatchProc_t pDraw, const TLine2D &sLine);
		void Lines2d(void *pParam, PDrawBatchProc_t pDraw, const TLine2D *pLines, uint uCount);

		// GL_LINE_STRIP //
		void Lines2dS(const TVertex2D *pPoints, uint uCount);
		void Lines2dS(void *pParam, PDrawBatchProc_t pDraw, const TVertex2D *pPoints, uint uCount);

		// GL_TRIANGLES //
		void Triangle2d(const TTriangle2D &sTriangle);
		void Triangle2d(void *pParam, PDrawBatchProc_t pDraw, const TTriangle2D &sTriangle);
		void Rect2d(const TRect2D &sRect);
		void Rect2d(void *pParam, PDrawBatchProc_t pDraw, const TRect2D &sRect);
		void Quad2dT(const TQuad2D &sQuad);							// triangles
		void Quad2dT(void *pParam, PDrawBatchProc_t pDraw, const TQuad2D &sQuad);
		void Quad2dG(const TQuad2D &sQuad);							// point (build quad in geometry shader)
		void Quad2dG(void *pParam, PDrawBatchProc_t pDraw, const TQuad2D &sQuad);

		// GL_TRIANGLE_STRIP //
		void Quad2dS(const TQuad2D &sQuad);
		void Quad2dS(void *pParam, PDrawBatchProc_t pDraw, const TQuad2D &sQuad);
		void Rect2dS(const TRect2D &sRect);
		void Rect2dS(void *pParam, PDrawBatchProc_t pDraw, const TRect2D &sRect);



		//---- 2D Render With Scissor ----//
		
		// GL_TRIANGLES //
		void ScissorQuad2dT(const TQuad2D &sQuad);
		void ScissorQuad2dT(void *pParam, PDrawBatchProc_t pDraw, const TQuad2D &sQuad);
		void ScissorQuad2dG(const TQuad2D &sQuad);
		void ScissorQuad2dG(void *pParam, PDrawBatchProc_t pDraw, const TQuad2D &sQuad);
		
		// GL_TRIANGLE_STRIP //
		void ScissorQuad2dS(const TQuad2D &sQuad);
		void ScissorQuad2dS(void *pParam, PDrawBatchProc_t pDraw, const TQuad2D &sQuad);
	};





//--------------------------- GLBatchRender ------------------------//
/*
=================================================
	создает новый батч
=================================================
*/
	inline void GLBatchRender::NewBatch(void *pParam, PDrawBatchProc_t pDraw, gl_primitive::type eMode, usize uBatchTypeIndex, float fDepth)
	{
		ASSERT( pDraw != null and uBatchTypeIndex < _aBatchTypes.Count() );
		
		const uint	u_vert_size = _aBatchTypes[ uBatchTypeIndex ]._uVertexSize;
		_aVertices.Resize( ( _aVertices.Size() / u_vert_size ) * u_vert_size );

		_aBatches.PushBack( TBatch( eMode, pParam, pDraw, _aVertices.Size(), uBatchTypeIndex, fDepth ) );
		
		NewElement();
	}

/*
=================================================
	сравнивает с предыдущим, если не совпадают,
	то создает новый батч
=================================================
*/
	inline void GLBatchRender::NewBatchC(void *pParam, PDrawBatchProc_t pDraw, gl_primitive::type eMode, usize uBatchTypeIndex, float fDepth)
	{
		ASSERT( pDraw != null and uBatchTypeIndex < _aBatchTypes.Count() );

		if ( _aBatches.Empty() or
			(_aBatches.Back().eMode		 != eMode			or
			 _aBatches.Back().uBatchType != uBatchTypeIndex	or
			 _aBatches.Back().pDrawProc	 != pDraw			or
			 _aBatches.Back().pParameter != pParam			or
			 !Equals( fDepth, _aBatches.Back().fDepth ) ) )
		{
			const uint	u_vert_size = _aBatchTypes[ uBatchTypeIndex ]._uVertexSize;
			_aVertices.Resize( ( _aVertices.Size() / u_vert_size ) * u_vert_size );

			_aBatches.PushBack( TBatch( eMode, pParam, pDraw, _aVertices.Size(), uBatchTypeIndex, fDepth ) );
		}
		
		NewElement();
	}
	
/*
=================================================
	добавляет вершины в буфер
=================================================
*/
	inline void GLBatchRender::AddVertices(const void *pVerts, usize uSize)
	{
		_aBatches.Back().uVerSize += uSize;
		_aVertices.Append( uni_array< const uint8 >::FromVoid( pVerts, uSize ) );
		_bChanged = true;
	}

//-------------------------------------------------------------------

}	// GLShell
}	// UX_STL

#endif	// _UXS_GL_BATCH_RENDER_H